/**
 * @class 黑洞实体
 * @author DuskyHuang 圣子
*/

import { _decorator, Node, Camera, MeshRenderer, Component, Vec3, renderer, gfx, Color, Vec4, Vec2 } from 'cc';
import { ICelestialSpecific } from '../celestial';
import { CustomizedLOD } from '../entities/customized-lod';
import { VisitProxy, cosmos, solar, ui, visit } from 'proxies';
import { CelestialLabel } from '../ui/items/celestial-label';
import { setLabelStyle, setBlackholeAppearance } from 'helpers';
import { attenuate } from 'algorithms';
import { utils } from 'cfw';
import { SceneGraph } from 'const';
import { Star } from './star';
import { findComp } from '../../../../cfw/utils';
import { EDITOR } from 'cc/env';
import { StarPrecise } from './star-precise';
const { ccclass, property, executeInEditMode } = _decorator;
const { serialize, group } = utils;

@ccclass('cocosmos.black-hole')
@executeInEditMode(true)
export class Blackhole extends CustomizedLOD implements ICelestialSpecific {

    @group("吸积盘设置", "盘面内侧颜色", Color)
    public get diskColorStart(): Color { return this._dc_start; }
    public set diskColorStart(arg: Color) { this._dc_start.set(arg); EDITOR && this.updateDisks(); }
    @serialize
    private _dc_start = Color.WHITE.clone();

    @group("吸积盘设置", "盘面外侧颜色", Color)
    public get diskColorFade(): Color { return this._dc_fade; }
    public set diskColorFade(arg: Color) { this._dc_fade.set(arg); EDITOR && this.updateDisks(); }
    @serialize
    private _dc_fade = Color.WHITE.clone();

    @group("吸积盘设置", "盘面宽度", Vec4)
    public get diskAttenuation(): Vec4 { return this._d_attr; }
    public set diskAttenuation(arg: Vec4) { this._d_attr.set(arg); EDITOR && this.updateDisks(); }
    @serialize
    private _d_attr = new Vec4(0, 0.15, 0.5, 1);

    @group("吸积盘设置", "盘面亮度", [0, 5, 0.1])
    public get diskBrightness(): number { return this._d_bright; }
    public set diskBrightness(arg: number) { this._d_bright = arg; EDITOR && this.updateDisks(); }
    @serialize
    private _d_bright = 1;

    @group("吸积盘设置", "盘面对比度", [0, 1, 0.1])
    public get diskContrast(): number { return this._d_contr; }
    public set diskContrast(arg: number) { this._d_contr = arg; EDITOR && this.updateDisks(); }
    @serialize
    private _d_contr = 0.5;

    @group("吸积盘设置", "盘面尘埃密度", [0, 5, 0.1])
    public get diskDensity(): Vec2 { return this._d_dens; }
    public set diskDensity(arg: Vec2) { this._d_dens.set(arg); EDITOR && this.updateDisks(); }
    @serialize
    private _d_dens = new Vec2(1, 1);

    @group("吸积盘设置", "盘面环绕流速", [0, 10, 0.1])
    public get diskFlowSpeed(): Vec2 { return this._d_flow; }
    public set diskFlowSpeed(arg: Vec2) { this._d_flow.set(arg); EDITOR && this.updateDisks(); }
    @serialize
    private _d_flow = new Vec2(4, 10);

    @group("相对论喷流设置", "喷流内侧颜色", Color)
    public get jetColorStart(): Color { return this._jc_start; }
    public set jetColorStart(arg: Color) { this._jc_start.set(arg); EDITOR && this.updateDisks(); }
    @serialize
    private _jc_start = Color.WHITE.clone();

    @group("相对论喷流设置", "喷流外侧颜色", Color)
    public get jetColorFade(): Color { return this._jc_fade; }
    public set jetColorFade(arg: Color) { this._jc_fade.set(arg); EDITOR && this.updateJets(); }
    @serialize
    private _jc_fade = Color.WHITE.clone();

    @group("相对论喷流设置", "喷流速度", Vec2)
    public get jetFlowSpeed(): Vec2 { return this._j_flow; }
    public set jetFlowSpeed(arg: Vec2) { this._j_flow.set(arg); EDITOR && this.updateJets(); }
    @serialize
    private _j_flow = new Vec2(2, 5);

    @group("相对论喷流设置", "喷流宽高", [0.1, 5, 0.1])
    public get jetCylinderScale(): Vec2 { return this._j_scale; }
    public set jetCylinderScale(arg: Vec2) { this._j_scale.set(arg); EDITOR && this.updateJets(); }
    @serialize
    private _j_scale = new Vec2(2, 5);

    @group("撕裂伴星设置", "卷吸恒星侧颜色", Color)
    public get vortexColorStar(): Color { return this._vc_star; }
    public set vortexColorStar(arg: Color) { this._vc_star.set(arg); EDITOR && this.updateVortexStar(); }
    @serialize
    private _vc_star = Color.WHITE.clone();

    @group("撕裂伴星设置", "卷吸吸积侧颜色", Color)
    public get vortexColorDisk(): Color { return this._vc_disk; }
    public set vortexColorDisk(arg: Color) { this._vc_disk.set(arg); EDITOR && this.updateVortexStar(); }
    @serialize
    private _vc_disk = Color.WHITE.clone();

    @group("撕裂伴星设置", "卷吸形态", Vec4)
    public get vortexAttenuation(): Vec4 { return this._v_attr; }
    public set vortexAttenuation(arg: Vec4) { this._v_attr.set(arg); EDITOR && this.updateVortexStar(); }
    @serialize
    private _v_attr = new Vec4(0, 0.15, 0.5, 1);

    @group("撕裂伴星设置", "卷吸流速")
    public get vortexFlowSpeed(): number { return this._v_speed; }
    public set vortexFlowSpeed(arg: number) { this._v_speed = arg; EDITOR && this.updateVortexStar(); }
    @serialize
    private _v_speed = 0.5;

    @group("撕裂伴星设置", "卷吸亮度", [0, 1, 0.1])
    public get vortexBrightness(): number { return this._v_brt; }
    public set vortexBrightness(arg: number) { this._v_brt = arg; EDITOR && this.updateVortexStar(); }
    @serialize
    private _v_brt = 0.5;

    @group("撕裂伴星设置", "卷吸对比度", [0, 1, 0.1])
    public get vortexContrast(): number { return this._v_contr; }
    public set vortexContrast(arg: number) { this._v_contr = arg; EDITOR && this.updateVortexStar(); }
    @serialize
    private _v_contr = 1;

    protected get LODValves(): num.pct[] { return BLACKHOLE_LOD_VALVES };
    public readonly cat: into = 'blackhole';
    public get config(): blackHoleConfig { return this._config; }

    public get nearest(): pow10 { return this._nearest + this._magnified; }
    public get suggest(): pow10 { return this.nearest + SUGGEST; }
    public get panorama(): pow10 { return this.nearest + 2; }
    public get farthest(): pow10 { return this._farthest; }
    public get star(): Star { return this._star; }
    public get vortex(): Node { return this._vortex; }
    public get jets(): Node { return this._jets; }
    public get viewer(): Camera { return this._viewer; }
    public get refer(): Node { return this._precise?.node ?? null; }
    public get precise(): StarPrecise { return this._precise; }
    public get scalar(): scalar { return this._scalar; }

    public get visible() { return this._visible; }
    public set visible(arg: boolean) {
        if (arg === this._visible) return;
        this.getComponentsInChildren(MeshRenderer).forEach(mesh => mesh.enabled = arg);
        this._visible = arg;
    }

    protected update(dt: time.sec): void {
        const { x: ws } = this.node.worldScale;
        this._disks_passes0.forEach((pass0, idx) => {
            pass0.setUniform(this._disks_vert_handlers[idx], ws);
        });
    }

    public loadConfig(config: blackHoleConfig, viewer: Camera, base: pow10): void {
        this._config = config;
        this._viewer = viewer;
        const { diam, dist, star } = config;
        this._real.set(diam, diam, diam);
        this.updateScale();
        this._nearest = Math.log10(diam) + base;
        this._farthest = Math.log10(dist * BLACKHOLE_FAR_RATIO);
        this._nearest = Math.log10(diam) + base;
        setBlackholeAppearance(this);
    }

    public loadReference(precise: StarPrecise): void {
        this._precise = precise;
    }

    protected init(): void {
        this._magnified = Math.log10(solar.getMagnified(this.cat));
        const disks = this.node.getChildByName("Disks");
        this._jets = this.node.getChildByName("Jets");
        this._disks = disks!.children;
        this._disks_mats = disks.getComponentsInChildren(MeshRenderer).map(mesh => mesh.getMaterialInstance(0));
        this._disks_passes0 = this._disks_mats.map(mat => mat.passes[0]);
        this._disks_vert_handlers = this._disks_passes0.map(pass => pass.getHandle('vertArgs', 0, gfx.Type.FLOAT));
        this._jets_mats = this._jets.getComponentsInChildren(MeshRenderer).map(mesh => mesh.getMaterialInstance(0));
        this._vortex = this.node.getChildByName("Vortex");
        this._vtx_mat = this._vortex.getComponent(MeshRenderer)!.getMaterialInstance(0);
        this._star = findComp(Star, this, 'Star')!;
    }

    protected onLod0Initiated(): void { }
    protected onTick(): void {
        this.syncNameLabel();
    }

    protected syncNameLabel(): void {
        if (this.node.activeInHierarchy && ui.showCelNames) {
            if (!this._label) {
                this._label = setLabelStyle(CelestialLabel.claim('solar', this.node.name), 'solar', 'blackhole');
                this._label.camera = this._viewer;
                this._label.target = this.node;
                const { name: en, cname: cn } = this._config;
                this._label.config = { en, cn }
                this._label.setUIOffset(0, 30);
            }
            this._label.alpha = attenuate(cosmos.pow10, BLACKHOLE_LABEL_ATT, false);
        } else this._freeLabel();
    }

    private _freeLabel(): void {
        if (this._label) {
            this._label.free();
            this._label = null;
        }
    }

    protected updateScale(): void {
        this.node.setScale(this._real);
    }

    public updateDisks(): void {
        this._disks_mats.forEach(mat => mat.setProperty('startColor', this._dc_start));
        this._disks_mats.forEach(mat => mat.setProperty('fadeColor', this._dc_fade));
        this._disks_mats.forEach(mat => mat.setProperty('attenuation', this._d_attr));
        v4a.set(this._d_bright, this._d_contr, this._d_dens.x, this._d_dens.y);
        this._disks_mats.forEach(mat => mat.setProperty('args1', v4a));
        v4b.x = this._d_flow.x; v4b.y = this._d_flow.y;
        this._disks_mats.forEach(mat => mat.setProperty('args0', v4b));
    }

    public updateJets(): void {
        this._jets_mats.forEach(mat => mat.setProperty('startColor', this._jc_start));
        this._jets_mats.forEach(mat => mat.setProperty('fadeColor', this._jc_fade));
        v4c.x = this._j_flow.x; v4c.y = this._j_flow.y;
        this._jets_mats.forEach(mat => mat.setProperty('args', v4c));
        this._jets.scale.set(this._j_scale.x, this._j_scale.y, this._j_scale.x);
    }

    public updateVortexStar(): void {
        this._vtx_mat.setProperty('startColor', this._vc_star);
        this._vtx_mat.setProperty('fadeColor', this._vc_disk);
        this._vtx_mat.setProperty('attenuation', this._v_attr);
        const pass0 = this._vtx_mat.passes[0];
        const brt: handle = pass0.getHandle('args1', 0, gfx.Type.FLOAT);
        const cst: handle = pass0.getHandle('args1', 1, gfx.Type.FLOAT);
        const spd: handle = pass0.getHandle('args0', 0, gfx.Type.FLOAT);
        pass0.setUniform(brt, this._v_brt);
        pass0.setUniform(cst, this._v_contr);
        pass0.setUniform(spd, this._v_speed);
    }

    private _label: CelestialLabel = null;
    private _config: blackHoleConfig;
    private _visible: boolean = false;
    private _nearest: pow10 = 0;
    private _farthest: pow10 = 0;
    private _magnified: pow10 = 0;
    private _disks_mats: renderer.MaterialInstance[] = [];
    private _disks_passes0: renderer.Pass[] = [];
    private _disks_vert_handlers: handle[] = [];
    private _jets_mats: renderer.MaterialInstance[] = [];
    private _vtx_mat: renderer.MaterialInstance = null;
    private _jets: Node;
    private _star: Star;
    private _vortex: Node;
    private _disks: Node[];
    private _precise: StarPrecise = null
    private _scalar: scalar = 'Stellar';
}

const { COSMOS, Cameras: { MAIN }, MAIN_LIGHT } = SceneGraph;
const BLACKHOLE_LABEL_ATT: attenuation = [-6.5, -5.5, -2.5, -1.5];
const { LensOffsets: { SUGGEST } } = VisitProxy;
const BLACKHOLE_LOD_VALVES = [0.1];
const BLACKHOLE_FAR_RATIO = 2.2;
const v4a = new Vec4;
const v4b = new Vec4(0, 0, 0, 0.1);
const v4c = new Vec4(0, 0, 0, 0.1);